/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright (c) 2008-2009, The KiWi Project (http://www.kiwi-project.eu)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 * - Redistributions of source code must retain the above copyright notice, 
 *   this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright notice, 
 *   this list of conditions and the following disclaimer in the documentation 
 *   and/or other materials provided with the distribution.
 * - Neither the name of the KiWi Project nor the names of its contributors 
 *   may be used to endorse or promote products derived from this software 
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 * Contributor(s):
 * sschaffe
 * 
 */
package kiwi.core.services.triplestore;

import kiwi.core.api.triplestore.TripleStorePersister;
import kiwi.core.model.rdf.KiWiNamespace;
import kiwi.core.model.rdf.KiWiNode;
import kiwi.core.model.rdf.KiWiTriple;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import java.io.Serializable;
import java.util.Collection;

/**
 * The TripleStorePersister is a helper component for the TripleStore and the KiWiSynchronization.
 * <p>
 * The TripleStorePersister really stores the triple data in the database and Sesame repository, 
 * while the TripleStore itself performs all operations only in memory, to be persisted on
 * transaction commit by KiWiSynchronization.beforeCompletion()
 * <p>
 * TripleStorePersister should only be used by developers that know what they are doing. All triple
 * operations should be accessed solely through TripleStore itself.
 *
 * @author Sebastian Schaffert
 *
 */
public class TripleStorePersisterImpl implements TripleStorePersister, Serializable {

    /**
     * Get the seam logger for issuing logging statements.
     */
    private Logger log = LoggerFactory.getLogger(TripleStorePersisterImpl.class);

    private EntityManager entityManager;


    public TripleStorePersisterImpl(EntityManager em) {
        this.entityManager = em;
    }

    /**
     * Store a node in the database by calling entityManager.persist; should only be called on transaction commit.
     *
     * @param node
     */
    @Override
    public void storeNode(KiWiNode node) {
        if(node.isDeleted()) {
            throw new IllegalArgumentException("An attemt to mark a node undeleted in the database that is flagged as deleted: "+node);
        }

        if(node.getId() == null) {
            entityManager.persist(node);
        } else if(!node.isDeleted()) {
            // update immutable entity in database ...
            Query q = entityManager.createNamedQuery("tripleStore.markUndeleted");
            q.setParameter("id", node.getId());
            int count = q.executeUpdate();
            if(count == 0) {
                log.error("error while undeleting node: #0",node);
            }
        }
    }


    /**
     * Mark a node in the database as deleted by updating the node.deleted column; should only be called on transaction
     * commit.
     *
     * @param node
     */
    @Override
    public void removeNode(KiWiNode node) {
        if(!node.isDeleted()) {
            throw new IllegalArgumentException("An attemt to mark a node deleted in the database that is not flagged as deleted: "+node);
        }


        if(node.getId() == null) {
            entityManager.persist(node);
        } else if(node.getId() != null && node.isDeleted()) {
            // update immutable entity in database ...
            Query q = entityManager.createNamedQuery("tripleStore.markDeleted");
            q.setParameter("id", node.getId());
            int count = q.executeUpdate();
            if(count == 0) {
                log.error("error while deleting node: #0",node);
            }
        }
    }

    /**
     * Store a triple in the database and Sesame repository by calling entityManager.persist and
     * RepositoryConnection.add; should only be called on transaction commit.
     *
     * @param triple
     */
    @Override
    public void storeTriple(KiWiTriple triple) {
        if(triple.getId() == null) {
            entityManager.persist(triple);
        }
    }



    /**
     * Batch operation to store a collection of triples in the database and Sesame repository by
     * calling entityManager.persist and RepositoryConnection.add; should only be called on
     * transaction commit.
     *
     * @param triples
     */
    @Override
    public void storeTriples(Collection<KiWiTriple> triples) {
        for(KiWiTriple triple : triples) {
            storeTriple(triple);
        }
    }

      /**
     * Mark a triple as deleted in the database and remove it from the Sesame repository. Should
     * only be called on transaction commit.
     *
     * @param triple
     */
    @Override
    public void removeTriple(KiWiTriple triple) {

        if(triple.getId() != null) {

            // update loaded entity (no effect to database, as KiWiTriple is immutable)
            triple.setDeleted(true);

            // update immutable entity in database ...
            Query q = entityManager.createNamedQuery("tripleStore.deleteTriple");
            q.setParameter("id", triple.getId());
            int count = q.executeUpdate();
            if(count == 0) {
                log.error("error while deleting triple #0",triple);
            }
        } else {
            log.warn("Asked to remove a base triple with a null id: ", triple);
        }

    }

    /**
     * Batch operation to mark a collection of triples as deleted in the database and remove them
     * from the Sesame repository. Should only be called on transaction commit.
     * @param triples
     */
    @Override
    public void removeTriples(Collection<KiWiTriple> triples) {
        // TODO: implement as batch operation!
        for(KiWiTriple t : triples) {
            removeTriple(t);
        }
    }


    /**
     * Store a namespace in the database and Sesame repository by calling entityManager.persist and
     * RepositoryConnection.setNamespace. Should only be called on transaction commit.
     * @param ns
     */
    @Override
    public void storeNamespace(KiWiNamespace ns) {
        log.debug("Storing namespace #0 : #1 ", ns.getPrefix(), ns.getUri());
        if(ns.getId() == null) {
             log.debug("Persisting namespace #0 : #1 ", ns.getPrefix(), ns.getUri());
             entityManager.persist(ns);
         } else {
             removeNamespace(ns);
             KiWiNamespace nns = new KiWiNamespace(ns.getPrefix(),ns.getUri());
             entityManager.persist(nns);

             log.debug("removing and restoring immutable namespace #0 : #1 ", ns.getPrefix(), ns.getUri());
         }
    }

    /**
     * Mark a namespace as deleted in the database and remove it from the Sesame repository by
     * calling RepositoryConnection.removeNamespace. Should only be called on transaction commit.
     * @param ns
     */
    @Override
    public void removeNamespace(KiWiNamespace ns) {
        log.debug("Removing namespace #0 : #1 ", ns.getPrefix(), ns.getUri());
        if(ns.getId() == null) {
            // does this ever happen? Hopefully not...
            ns.setDeleted(true);

            entityManager.persist(ns);
            log.warn("persisted removed namespace, which does not really make sense (#0)",ns.toString());
        } else {
            // for the cache (not persisted, immutable entity...)
            ns.setDeleted(true);

            // update immutable entity in database ...
            Query q = entityManager.createNamedQuery("tripleStore.deleteNamespace");
            q.setParameter("id", ns.getId());
            int count = q.executeUpdate();
            if(count == 0) {
                log.error("error while deleting namespace #0 in database",ns);
            }
        }

    }



}
